#include <assert.h>
#include <stdlib.h>
#include <memory.h>
#include "hrd_core.h"
#include "hrd_logger.h"
#include "hrd_loader.h"
#include "hrd_bitmask.h"
#include "hrd_displayer.h"
#include "hrd_solver.h"


static hrd_problem_t *newProblem()
{
    hrd_problem_t *problem = NULL;

    problem = malloc(sizeof(hrd_problem_t));
    if (problem == NULL) {
        LOG_FATAL("Unable to allocate memory for new problem.");
        return NULL;
    }

    HrdBitmask_InitBoundary(problem->bitmask_list);
    problem->object_allowance_list[MAP_BITMASK_INDEX].allowed_directions_number = 0;

    return problem;
}


void HrdCore_DestroyProblem(hrd_problem_t *hrd_problem)
{
    assert(hrd_problem != NULL);

    free(hrd_problem);
}


hrd_problem_t *HrdCore_LoadProblem(char *json_filename)
{
    assert(json_filename != NULL);

    hrd_problem_t *problem = newProblem();
    if (problem == NULL) {
        LOG_FATAL("Problem not loaded.");
        return NULL;
    }

    if (HrdLoader_LoadProblem(json_filename, problem) == NULL) {
        HrdCore_DestroyProblem(problem);
        return NULL;
    }
    HrdBitmask_InitMap(problem->bitmask_list);

    return problem;
}


bool HrdCore_IsGoalAchieved(hrd_problem_t *hrd_problem)
{
    assert(hrd_problem != NULL);

    return HrdBitmask_IsGoalAchieved(hrd_problem->goal, hrd_problem->bitmask_list);
}


static bool isActionAllowed(hrd_problem_t *hrd_problem, hrd_action_t action)
{
    uint8_t i, index = action.object_index;
    hrd_object_allowance_t *p_object_allowance = &(hrd_problem->object_allowance_list[index]);

    for (i = 0; i < p_object_allowance->allowed_directions_number; i++) {
        if (action.direction == p_object_allowance->allowed_directions[i]) {
            return true;
        }
    }

    return false;
}


hrd_take_action_ret_et HrdCore_TakeAction(hrd_problem_t *hrd_problem, hrd_action_t action)
{
    assert(hrd_problem != NULL);

    uint8_t i, index;
    hrd_bitmask_t old_object_bitmask, new_object_bitmask;

    if ((index = action.object_index) >= hrd_problem->object_number) {
        return NO_SUCH_OBJECT;
    }
    if (!isActionAllowed(hrd_problem, action)) {
        return ACTION_NOT_ALLOWED;
    }

    if (!HrdBitmask_CanUpdateObject(hrd_problem->bitmask_list, action.object_index, action.direction, action.length)) {
        return ACTION_CANNOT_BE_DONE;
    }
    HrdBitmask_UpdateObject(hrd_problem->bitmask_list, action.object_index, action.direction, action.length);

    return ACTION_TOOK;
}


void HrdCore_ShowColorMapping(hrd_problem_t *hrd_problem)
{
    assert(hrd_problem != NULL);

    HrdDisplayer_ShowColorMapping(hrd_problem);
}


void HrdCore_DisplayMapInColorizeBlock(hrd_problem_t *hrd_problem, bool show_grid)
{
    assert(hrd_problem != NULL);

    HrdDisplayer_DisplayMapInColorizeBlock(hrd_problem, show_grid);
}


void HrdCore_DisplayMapInCharacter(hrd_problem_t *hrd_problem, bool show_grid)
{
    assert(hrd_problem != NULL);

    HrdDisplayer_DisplayMapInCharacter(hrd_problem, show_grid);
}


uint8_t HrdCore_SolveProblem(hrd_problem_t *hrd_problem, hrd_action_t *solution)
{
    assert(hrd_problem != NULL);
    assert(solution != NULL);

    uint8_t real_path_length = 0;
    hrd_path_t solution_path = HrdSolver_Solve(hrd_problem);

    if (solution_path.path_length == 0) {
        return 0;
    }

    real_path_length = solution_path.path_length - 1 /* One is for the inital append action. */;
    memcpy(solution, &solution_path.path[1], sizeof(hrd_action_t) * real_path_length);

    return real_path_length;
}